﻿namespace HuwmanCode.ComponentModel.Composition

open System
open System.ComponentModel.Composition
open System.ComponentModel.Composition.Hosting
open System.ComponentModel.Composition.Primitives
open System.Reflection
open System.ServiceModel
open System.ServiceModel.Channels
open System.ServiceModel.Dispatcher
open System.ServiceModel.Description
open System.Threading

open HuwmanCode
open HuwmanCode.ComponentModel.Composition.Hosting
open HuwmanCode.Diagnostics.Logging

type CompositionEventArgs (container:CompositionContainer, context:string) =
    inherit EventArgs ()

    member this.Container = container
    member this.Context = context

type public CompositionEventDelegate = delegate of obj * CompositionEventArgs -> unit

type ComposeInstanceEventArgs (container:CompositionContainer, instance, context:string) =
    inherit EventArgs () 

    member this.Container = container
    member this.Instance = instance
    member this.Context = context

type public CompositionInstanceEventDelegate = delegate of obj * ComposeInstanceEventArgs -> unit

type MefCompositionServiceBehavior (catalog, providers) as this =
    let syncLock = new Object ()

    let container' = ref Unchecked.defaultof<CompositionContainer>
    let instanceCreated = new Event<CompositionInstanceEventDelegate,_> ()
    let instanceComposed = new Event<CompositionInstanceEventDelegate,_> ()
    let instanceReleased = new Event<CompositionInstanceEventDelegate,_> ()
    let containerCreated = new Event<CompositionEventDelegate,_> ()
    let containerDisposing = new Event<CompositionEventDelegate,_> ()
    
    let getContainer context =
        lock syncLock (fun _ ->
            if container'.Value = null then
                container' := new CompositionContainer (catalog, providers)
                containerCreated.Trigger (this, new CompositionEventArgs (container'.Value, context))
            container'.Value)

    let releaseContainer context =
        lock syncLock (fun _ ->
            if container'.Value <> null then 
                containerDisposing.Trigger (this, new CompositionEventArgs (container'.Value, context))
                container'.Value.Dispose ())

    [<CLIEvent>]
    member this.InstanceCreated = instanceCreated.Publish
    [<CLIEvent>]
    member this.InstanceComposed = instanceComposed.Publish
    [<CLIEvent>]
    member this.InstanceReleased = instanceReleased.Publish
    [<CLIEvent>]
    member this.ContainerCreated = containerCreated.Publish
    [<CLIEvent>]
    member this.ContainerDisposing = containerDisposing.Publish

    interface IServiceBehavior with
        member this.Validate (description, serviceHost) = 
            serviceHost.Closed |> Observable.add (fun _ -> releaseContainer description.Name)

        member this.AddBindingParameters (description, serviceHost, endpoints, parameters) = ()

        member this.ApplyDispatchBehavior (description, serviceHost) = 
            serviceHost.ChannelDispatchers
            |> Seq.choose (fun cd -> cd :? ChannelDispatcher |> Bool.toOption ((cd :?> ChannelDispatcher).Endpoints))
            |> Seq.concat
            |> Seq.iter (fun ep -> 
                ep.DispatchRuntime.InstanceProvider <-
                    { new IInstanceProvider with
                        member ip.GetInstance (instanceContext, message) =
                            ip.GetInstance (instanceContext)

                        member ip.GetInstance (instanceContext) =
                            let serviceType = instanceContext.Host.Description.ServiceType
                            let instance = Activator.CreateInstance serviceType
                            lock syncLock (fun _ ->
                                let container = getContainer description.Name
                                instanceCreated.Trigger (this, new ComposeInstanceEventArgs (container, instance, description.Name))

                                container.ComposeParts (instance)
                                instanceComposed.Trigger (this, new ComposeInstanceEventArgs (container, instance, description.Name))
                                instance)

                        member ip.ReleaseInstance (instanceContext, instance) =
                            match instance with
                            | :? IDisposable as Disposable -> Disposable.Dispose ()
                            | _ -> () 
                            lock syncLock (fun _ ->
                                let container = getContainer description.Name
                                instanceReleased.Trigger (this, new ComposeInstanceEventArgs (container, instance, description.Name))) })
